<?php

declare(strict_types=1);

namespace app\common\service;

use app\common\exception\AdminException;
use Elliptic\EC;
use kornrunner\Keccak;
use think\facade\Cache;
use think\facade\Log;

class BSCSignatureService
{
    // 签名相关常量
    private const SIGNATURE_LENGTH = 130;
    private const NONCE_LENGTH = 32;
    private const TIMESTAMP_TOLERANCE = 300;

    /**
     * 验证BSC签名
     */
    public static function verifySignature(string $message, string $signature, string $address): bool
    {
        try {
            // 1. 计算消息的哈希
            $messageHash = self::hashMessage($message);

            // 2. 从签名恢复公钥和地址
            $recoveredAddress = self::recoverAddressFromSignature($messageHash, $signature);

            // 3. 比较地址（不区分大小写）
            return strtolower($recoveredAddress) === strtolower($address);
        } catch (\Exception $e) {
            Log::error('BSC签名验证失败: ' . $e->getMessage());
            return false;
        }
    }

    /**
     * 计算以太坊风格的消息哈希
     */
    private static function hashMessage(string $message): string
    {
        $prefix = "\x19Ethereum Signed Message:\n" . strlen($message);
        $prefixedMessage = $prefix . $message;

        return hex2bin(Keccak::hash($prefixedMessage, 256));
    }

    /**
     * 从签名恢复地址 - 修复版本
     */
    private static function recoverAddressFromSignature(string $messageHash, string $signature): string
    {
        // 1. 判空 & 清理
        if (empty($signature)) {
            throw new \Exception('Signature is null or empty');
        }

        $originalSig = $signature;
        $signature = trim($signature);
        $signature = str_replace(['0x', '0X'], '', $signature);

        // 2. 验证长度
        if (strlen($signature) !== self::SIGNATURE_LENGTH) {
            throw new \Exception("Invalid signature length: " . strlen($signature) . ", expected " . self::SIGNATURE_LENGTH . ". Raw: [$originalSig]");
        }

        // 3. 截取 r, s, v
        $rHex = substr($signature, 0, 64);
        $sHex = substr($signature, 64, 64);
        $vHex = substr($signature, 128, 2);

        // 4. 严格校验是否为合法 hex
        if (!ctype_xdigit($rHex) || !ctype_xdigit($sHex) || !ctype_xdigit($vHex)) {
            throw new \Exception("Invalid signature format: contains non-hex characters");
        }

        // 5. 解析 v
        $v = hexdec($vHex);
        $recid = $v >= 27 ? $v - 27 : $v;

        if ($recid !== 0 && $recid !== 1) {
            throw new \Exception("Invalid recid: $recid, derived from v=$v");
        }

        // 6. 恢复公钥 - 完全避免使用GMP
        $ec = new EC('secp256k1');
        try {
            // 直接将十六进制字符串转换为十进制大整数字符串
            $rDec = self::hexToDec($rHex);
            $sDec = self::hexToDec($sHex);

            $pubKey = $ec->recoverPubKey($messageHash, [
                'r' => $rDec,
                's' => $sDec
            ], $recid);

        } catch (\Exception $e) {
            // 如果上面的方法失败，尝试使用字符串方法
            try {
                $pubKey = $ec->recoverPubKey($messageHash, [
                    'r' => $rHex,
                    's' => $sHex
                ], $recid);
            } catch (\Exception $e2) {
                throw new \Exception("ECDSA public key recovery failed: " . $e2->getMessage() . " | First attempt: " . $e->getMessage());
            }
        }

        return self::publicKeyToAddress($pubKey);
    }

    /**
     * 将十六进制转换为十进制大整数字符串
     */
    private static function hexToDec(string $hex): string
    {
        // 移除可能的0x前缀
        $hex = str_replace('0x', '', $hex);

        $dec = '0';
        $len = strlen($hex);

        for ($i = 0; $i < $len; $i++) {
            $current = hexdec($hex[$i]);
            $dec = bcmul($dec, '16', 0);
            $dec = bcadd($dec, (string)$current, 0);
        }

        return $dec;
    }

    /**
     * 将公钥转换为BSC地址
     */
    private static function publicKeyToAddress($publicKey): string
    {
        $publicKeyHex = $publicKey->encode('hex');
        $publicKeyBytes = substr($publicKeyHex, 2); // 移除04前缀
        $hash = Keccak::hash(hex2bin($publicKeyBytes), 256);
        return '0x' . substr($hash, -40);
    }

    /**
     * 替代方案：使用更简单的方法恢复地址
     */
    public static function verifySignatureSimple(string $message, string $signature, string $address): bool
    {
        try {
            // 使用现有的库来简化验证过程
            $msg = "\x19Ethereum Signed Message:\n" . strlen($message) . $message;
            $hash = Keccak::hash($msg, 256);

            $sign = [
                "r" => substr($signature, 2, 64),
                "s" => substr($signature, 66, 64)
            ];

            $v = ord(hex2bin(substr($signature, 130, 2)));
            if ($v >= 27) {
                $v -= 27;
            }

            $ec = new EC('secp256k1');
            $pubKey = $ec->recoverPubKey($hash, $sign, $v);
            $recoveredAddress = self::publicKeyToAddress($pubKey);
            return strtolower($address) === strtolower($recoveredAddress);
        } catch (\Exception $e) {
            Log::error('简单签名验证失败: ' . $e->getMessage());
            return false;
        }
    }

    /**
     * 生成并存储nonce
     */
    public static function generateNonce(string $key, int $expire = 300): string
    {
        $nonce = bin2hex(random_bytes(16));
        Cache::set('bsc_nonce:' . $key, $nonce, $expire);
        return $nonce;
    }

    /**
     * 验证nonce
     */
    public static function verifyNonce(string $key, string $nonce, bool $consume = true): bool
    {
        $cacheKey = 'bsc_nonce:' . $key;
        $storedNonce = Cache::get($cacheKey);

        if (!$storedNonce) {
            Log::error('Nonce验证失败: 未找到存储的nonce，key=' . $key);
            return false;
        }

        if ($storedNonce !== $nonce) {
            Log::error('Nonce验证失败: nonce不匹配，期望=' . $storedNonce . '，实际=' . $nonce);
            return false;
        }

        if ($consume) {
            Cache::delete($cacheKey);
        }

        return true;
    }

    /**
     * 构建签名消息
     */
    public static function buildMessage(string $nonce, string $additionalInfo = ''): string
    {
        $timestamp = time();
        $message = "Welcome to our dApp!\n\n";
        $message .= "Please sign this message to verify your ownership.\n";
        $message .= "This will not cost any gas fees.\n\n";
        $message .= "Nonce: " . $nonce . "\n";
        $message .= "Timestamp: " . $timestamp;

        if (!empty($additionalInfo)) {
            $message .= "\nAdditional Info: " . $additionalInfo;
        }

        return $message;
    }

    /**
     * 从消息中提取nonce
     */
    public static function extractNonceFromMessage(string $message): ?string
    {
        if (preg_match('/Nonce:\s*([a-f0-9]{32})/i', $message, $matches)) {
            return $matches[1];
        }
        return null;
    }

    /**
     * 从消息中提取时间戳
     */
    public static function extractTimestampFromMessage(string $message): ?int
    {
        if (preg_match('/Timestamp:\s*(\d+)/', $message, $matches)) {
            return (int)$matches[1];
        }
        return null;
    }

    /**
     * 验证时间戳是否在允许范围内
     */
    public static function validateTimestamp(?int $timestamp, int $tolerance = null): bool
    {
        if ($timestamp === null) {
            return false;
        }

        $tolerance = $tolerance ?? self::TIMESTAMP_TOLERANCE;
        $currentTime = time();
        $timeDiff = abs($currentTime - $timestamp);

        return $timeDiff <= $tolerance;
    }

    /**
     * 完整的BSC验证流程
     */
    public static function completeVerify(
        string $address,
        string $signature,
        string $originalMessage
    ): array {
        try {
            // 1. 从消息中提取nonce
            $nonce = self::extractNonceFromMessage($originalMessage);
            if (!$nonce) {
                Log::error('BSC验证失败: 无法从消息中提取nonce');
                return ['success' => false, 'error' => 'Invalid message format'];
            }

            // 2. 验证nonce
            if (!self::verifyNonce($address, $nonce)) {
                Log::error('BSC验证失败: Nonce验证失败');
                return ['success' => false, 'error' => 'Invalid nonce'];
            }

            // 3. 验证时间戳
            $timestamp = self::extractTimestampFromMessage($originalMessage);
            if (!self::validateTimestamp($timestamp)) {
                Log::error('BSC验证失败: 时间戳验证失败，timestamp=' . $timestamp);
                return ['success' => false, 'error' => 'Message expired'];
            }

            // 4. 验证签名 - 使用简单版本
            $isSignatureValid = self::verifySignatureSimple($originalMessage, $signature, $address);

            if (!$isSignatureValid) {
                // 如果简单版本失败，尝试使用原始版本
                $isSignatureValid = self::verifySignature($originalMessage, $signature, $address);

                if (!$isSignatureValid) {
                    Log::error('BSC验证失败: 签名验证失败');
                    return ['success' => false, 'error' => 'Invalid signature'];
                }
            }

            return ['success' => true, 'nonce' => $nonce, 'address' => $address];

        } catch (\Exception $e) {
            Log::error('BSC验证异常: ' . $e->getMessage());
            return ['success' => false, 'error' => 'Verification exception'];
        }
    }
}